/**
* Copyright (c) 2011 Stefan Henss.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Stefan Henss - initial API and implementation.
* Patrick Gottschaemmer, Olav Lenz - externalize Strings.
*/
package org.eclipse.recommenders.internal.calls.rcp;
import static com.google.common.collect.Iterables.isEmpty;
import static java.text.MessageFormat.format;
import static org.eclipse.recommenders.calls.ICallModel.DefinitionKind.*;
import static org.eclipse.recommenders.internal.apidocs.rcp.ApidocsViewUtils.*;
import static org.eclipse.recommenders.rcp.JavaElementSelectionEvent.JavaElementSelectionLocation.METHOD_BODY;
import static org.eclipse.recommenders.rcp.utils.JdtUtils.resolveMethod;
import static org.eclipse.recommenders.utils.Recommendations.*;
import static org.eclipse.swt.SWT.COLOR_INFO_FOREGROUND;
import java.util.Set;
import javax.inject.Inject;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.recommenders.apidocs.rcp.ApidocProvider;
import org.eclipse.recommenders.apidocs.rcp.JavaSelectionSubscriber;
import org.eclipse.recommenders.calls.ICallModel;
import org.eclipse.recommenders.calls.ICallModel.DefinitionKind;
import org.eclipse.recommenders.calls.ICallModelProvider;
import org.eclipse.recommenders.internal.apidocs.rcp.ApidocsViewUtils;
import org.eclipse.recommenders.internal.calls.rcp.l10n.Messages;
import org.eclipse.recommenders.models.UniqueTypeName;
import org.eclipse.recommenders.models.rcp.IProjectCoordinateProvider;
import org.eclipse.recommenders.rcp.JavaElementResolver;
import org.eclipse.recommenders.rcp.JavaElementSelectionEvent;
import org.eclipse.recommenders.rcp.utils.JdtUtils;
import org.eclipse.recommenders.utils.Recommendation;
import org.eclipse.recommenders.utils.Recommendations;
import org.eclipse.recommenders.utils.names.IMethodName;
import org.eclipse.recommenders.utils.names.Names;
import org.eclipse.recommenders.utils.names.VmMethodName;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import com.google.common.annotations.Beta;
import com.google.common.base.Optional;
import com.google.common.collect.Sets;
import com.google.common.eventbus.EventBus;
@Beta
public final class CallsApidocProvider extends ApidocProvider {
private final ICallModelProvider modelProvider;
private final IProjectCoordinateProvider pcProvider;
private final JavaElementResolver jdtResolver;
private final EventBus workspaceBus;
@Inject
public CallsApidocProvider(ICallModelProvider modelProvider, IProjectCoordinateProvider pcProvider,
JavaElementResolver jdtResolver, EventBus workspaceBus) {
this.modelProvider = modelProvider;
this.pcProvider = pcProvider;
this.jdtResolver = jdtResolver;
this.workspaceBus = workspaceBus;
}
private IType receiverType;
private ICallModel model;
private UniqueTypeName baseName;
@JavaSelectionSubscriber
public void onVariableSelection(final ILocalVariable var, final JavaElementSelectionEvent event,
final Composite parent) {
handle(var, var.getElementName(), var.getTypeSignature(), event, parent);
}
@JavaSelectionSubscriber(METHOD_BODY)
public void onFieldSelection(final IField var, final JavaElementSelectionEvent event, final Composite parent)
throws JavaModelException {
handle(var, var.getElementName(), var.getTypeSignature(), event, parent);
}
private void handle(final IJavaElement variable, final String elementName, final String typeSignature,
final JavaElementSelectionEvent event, final Composite parent) {
final Optional<ASTNode> opt = event.getSelectedNode();
if (!opt.isPresent()) {
return;
}
final Optional<IType> varType = findVariableType(typeSignature, variable);
if (!varType.isPresent()) {
return;
}
receiverType = varType.get();
baseName = pcProvider.toUniqueName(receiverType).orNull();
if (baseName == null || !acquireModel()) {
return;
}
try {
final ASTNode node = opt.get();
final Optional<MethodDeclaration> optAstMethod = findEnclosingMethod(node);
final Optional<IMethod> optJdtMethod = resolveMethod(optAstMethod.orNull());
if (!optJdtMethod.isPresent()) {
return;
}
AstDefUseFinder defUse = new AstDefUseFinder(variable.getElementName(), optAstMethod.orNull());
IMethod findFirstDeclaration = JdtUtils.findFirstDeclaration(optJdtMethod.get());
IMethodName overrideContext = jdtResolver.toRecMethod(findFirstDeclaration).or(VmMethodName.NULL);
Set<IMethodName> calls = Sets.newHashSet(defUse.getCalls());
IMethodName definingMethod = defUse.getDefiningMethod().orNull();
DefinitionKind kind = defUse.getDefinitionKind();
// In the case of parameters we replace the defining method with the overridesContext
if (PARAM == kind) {
definingMethod = overrideContext;
}
if (kind == DefinitionKind.UNKNOWN) {
// if the ast resolver could not find a definition of the
// variable, it's a method return value? Ask
// the context and try.
if (definingMethod == null) {
kind = FIELD;
} else if (definingMethod.isInit()) {
kind = DefinitionKind.NEW;
} else {
kind = RETURN;
}
}
model.setObservedOverrideContext(overrideContext);
model.setObservedDefiningMethod(definingMethod);
model.setObservedCalls(calls);
model.setObservedDefinitionKind(kind);
Iterable<Recommendation<IMethodName>> methodCalls = sortByRelevance(Recommendations.filterRelevance(
model.recommendCalls(), 0.01 / 100));
runSyncInUiThread(new CallRecommendationsRenderer(overrideContext, methodCalls, calls,
variable.getElementName(), definingMethod, kind, parent));
} finally {
releaseModel();
}
}
private Optional<IType> findVariableType(final String typeSignature, final IJavaElement parent) {
return JdtUtils.findTypeFromSignature(typeSignature, parent);
}
private Optional<MethodDeclaration> findEnclosingMethod(final ASTNode node) {
MethodDeclaration declaringNode = null;
for (ASTNode p = node; p != null; p = p.getParent()) {
if (p instanceof MethodDeclaration) {
declaringNode = (MethodDeclaration) p;
break;
}
}
return Optional.fromNullable(declaringNode);
}
private boolean acquireModel() {
model = modelProvider.acquireModel(baseName).orNull();
return model != null;
}
private void releaseModel() {
if (model != null) {
modelProvider.releaseModel(model);
model = null;
}
}
private final class CallRecommendationsRenderer implements Runnable {
private final IMethodName ctx;
private final Iterable<Recommendation<IMethodName>> methodCalls;
private final Set<IMethodName> calls;
private final String varName;
private final IMethodName def;
private final DefinitionKind kind;
private final Composite parent;
private CallRecommendationsRenderer(IMethodName ctx, Iterable<Recommendation<IMethodName>> methodCalls,
Set<IMethodName> calls, String varName, IMethodName def, final DefinitionKind kind, Composite parent) {
this.ctx = ctx;
this.methodCalls = methodCalls;
this.calls = calls;
this.varName = varName;
this.def = def;
this.kind = kind;
this.parent = parent;
}
@Override
public void run() {
final Composite container = createComposite(parent, 4);
final Label preamble2 = new Label(container, SWT.NONE);
setInfoForegroundColor(preamble2);
setInfoBackgroundColor(preamble2);
preamble2.setLayoutData(GridDataFactory.swtDefaults().span(4, 1).indent(0, 0).create());
if (isEmpty(methodCalls)) {
preamble2.setText(format(Messages.PROVIDER_INTRO_NO_RECOMMENDATIONS, receiverType.getElementName(),
varName));
} else {
preamble2.setText(format(Messages.PROVIDER_INTRO_RECOMMENDATIONS, receiverType.getElementName(),
varName));
}
new Label(container, SWT.NONE).setLayoutData(GridDataFactory.swtDefaults().span(4, 1).indent(0, 0)
.hint(SWT.DEFAULT, 1).create());
for (final Recommendation<IMethodName> rec : methodCalls) {
createLabel(container, percentageToRecommendationPhrase(asPercentage(rec)), true, false,
COLOR_INFO_FOREGROUND, false);
createLabel(container, Messages.TABLE_CELL_RELATION_CALL + " ", false); //$NON-NLS-1$
ApidocsViewUtils.createMethodLink(container, rec.getProposal(), jdtResolver, workspaceBus);
double relevance = rec.getRelevance();
String format = relevance < 0.01d ? Messages.TABLE_CELL_SUFFIX_PROMILLE
: Messages.TABLE_CELL_SUFFIX_PERCENTAGE;
createLabel(container, " - " + format(format, relevance), false); //$NON-NLS-1$
}
new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);
createLabel(container, "", false); //$NON-NLS-1$
createLabel(container, "", false); //$NON-NLS-1$
createLabel(container, "", false); //$NON-NLS-1$
final Label preamble = new Label(container, SWT.NONE);
preamble.setLayoutData(GridDataFactory.swtDefaults().span(4, 1).indent(0, 5).create());
setInfoForegroundColor(preamble);
setInfoBackgroundColor(preamble);
final String text;
if (ctx == VmMethodName.NULL) {
text = format(Messages.PROVIDER_INFO_UNTRAINED_CONTEXT, receiverType.getElementName());
} else {
text = format(Messages.PROVIDER_INFO_LOCAL_VAR_CONTEXT, receiverType.getElementName(),
Names.vm2srcSimpleTypeName(ctx.getDeclaringType()) + "." + Names.vm2srcSimpleMethod(ctx)); //$NON-NLS-1$
}
preamble.setText(text);
new Label(container, SWT.NONE).setLayoutData(GridDataFactory.swtDefaults().span(4, 1).indent(0, 5)
.hint(SWT.DEFAULT, 1).create());
if (def != null) {
createLabel(container, Messages.TABLE_CELL_RELATION_DEFINED_BY, true, false, SWT.COLOR_DARK_GRAY, false);
createLabel(container, "", false, false, SWT.COLOR_DARK_GRAY, false); //$NON-NLS-1$
if (def == VmMethodName.NULL) {
createLabel(container, Messages.TABLE_CELL_DEFINITION_UNTRAINED, false, false, SWT.COLOR_DARK_GRAY,
false);
} else {
ApidocsViewUtils.createMethodLink(container, def, jdtResolver, workspaceBus);
}
createLabel(container, "- " + kind.toString().toLowerCase(), true, false, SWT.COLOR_DARK_GRAY, false); //$NON-NLS-1$
}
for (final IMethodName observedCall : calls) {
createLabel(container, Messages.TABLE_CELL_RELATION_OBSERVED, true, false, SWT.COLOR_DARK_GRAY, false);
createLabel(container, Messages.TABLE_CELL_RELATION_CALL + ' ', false, false, SWT.COLOR_DARK_GRAY,
false);
ApidocsViewUtils.createMethodLink(container, observedCall, jdtResolver, workspaceBus);
createLabel(container, "", true, false, SWT.COLOR_DARK_GRAY, false); //$NON-NLS-1$
}
}
}
}